/**
 * Copyright (c) 2009, ERICSSON AB
 *  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or 
 * without modification, are permitted provided that the following 
 * conditions are met:
 * - Redistributions of source code must retain the above copyright 
 *   notice, this list of conditions and the following disclaimer. 
 * - Redistributions in binary form must reproduce the above copyright 
 *   notice, this list of conditions and the following disclaimer in 
 *   the documentation and/or other materials provided with the 
 *   distribution.
 * - Neither the name ERICSSON AB nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 */
package com.ericsson.labs.wave.messy;

import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;

import com.ericsson.labs.wave.messy.model.Message;
import com.ericsson.labs.wave.messy.utils.HttpHelperUtil;
import com.ericsson.labs.wave.messy.utils.PMF;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Event;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.TextView;
import com.google.wave.api.Wavelet;

/**
 * The main servlet responsible to handle all types of Robot events. Due to a
 * bug (Issue 171) the main servlet is also responsible for handling cron job
 * update events.
 * 
 * @author stefan.alund@ericsson.com (Stefan Ålund)
 */
@SuppressWarnings("serial")
public class MessyTheWaveRobotServlet extends AbstractRobotServlet {

   private static final Logger log = Logger
         .getLogger(MessyTheWaveRobotServlet.class.getName());

   /**
    * URL to Ericsson Labs messaging infrastructure.
    */
   private static final String LABS_SEND_URL = "http://sms.labs.ericsson.net/send";

   /**
    * Developer key for SMS Send & Receive API.
    */
   private static final String KEY = "your key here";

   /**
    * The Robot usage instructions.
    */
   private static String USAGE_INSTRUCTIONS = "Hello, Messy here! Usage: <b>/sms</b> "
         + "phone_nr your_msg. <b>Example:</b> /sms 4673888999 Hi Sarah, SMSing from Wave!";

   /**
    * Invalid message format error.
    */
   private static final String ERROR_FORMAT = "Error: please indicate phone number and your message, "
         + "see usage above.";

   /**
    * The Robot command prefix.
    */
   private static String COMMAND_PREFIX = "/sms";

   /*
    * (non-Javadoc)
    * 
    * @see com.google.wave.api.AbstractRobotServlet#processEvents(com.google.wave.api.RobotMessageBundle)
    */
   @Override
   public void processEvents(RobotMessageBundle bundle) {
      Wavelet wavelet = bundle.getWavelet();

      if (bundle.wasSelfAdded()) {
         processWaveletSelfAddedEvent(wavelet);
      } else {
         // If the event is "Blip submitted", check for command prefix.
         for (Event event : bundle.getBlipSubmittedEvents()) {
            processBlipSubmittedEvent(event.getBlip(), wavelet);
         }
      }

      // A bug (?) in Wave calls /_wave/robot/jsonrpc instead of the correct one
      processCronUpdate(bundle);
   }

   /**
    * Display usage instructions in a new Blip.
    * 
    * @param wavelet
    *           the wavelet
    */
   private void processWaveletSelfAddedEvent(Wavelet wavelet) {
      // Displays instructions
      log.info("processWaveletSelfAddedEvent() called");
      Blip blip = wavelet.appendBlip();
      TextView textView = blip.getDocument();
      textView.appendMarkup(USAGE_INSTRUCTIONS);
   }

   /**
    * Processes the Blip submitted event. Checks for a properly formatted
    * message syntax.
    * 
    * @param blip
    *           the blip
    */
   private void processBlipSubmittedEvent(Blip blip, Wavelet wavelet) {
      // Initializes the response
      String response = null;

      // Retrieves the content of the blip
      String blipDocumentText = null;
      if (blip.getBlipId().equals(blip.getWavelet().getRootBlipId())) {
         blipDocumentText = blip.getWavelet().getTitle();
      } else {
         blipDocumentText = blip.getDocument().getText();
      }
      log.info("Received blip with text: '" + blipDocumentText + "'");

      // If this content is a robot command
      if (blipDocumentText != null
            && blipDocumentText.startsWith(COMMAND_PREFIX)) {
         // Extracts the address in the command
         String messageAndNumber = blipDocumentText.substring(
               COMMAND_PREFIX.length()).trim();

         if (messageAndNumber.length() > 0) {
            // Split the rest of the message into number and text.
            String[] parts = messageAndNumber.split(" ");
            if (parts.length < 2) {
               response = ERROR_FORMAT;
            } else {
               String number = parts[0];
               String msg = messageAndNumber.substring(number.length());

               Message _message = makeMessagePersistent(msg, number, blip,
                     wavelet);

               try {
                  response = sendTextMessage(_message);
               } catch (Exception e) {
                  response = "Error when sending SMS: " + e.getMessage();
               }
            }
         } else {
            response = ERROR_FORMAT;
         }
      }

      // Renders response
      if (response != null) {
         blip.getDocument().appendMarkup(response);
      }
   }

   /**
    * Store the message conversation in the data store.
    * 
    * @param msg
    *           The text message.
    * @param number
    *           The receiver phone number (MSISDN).
    * @param blip
    *           The blip.
    * @param wavelet
    *           The wavelet.
    * @return A persistent object.
    */
   private Message makeMessagePersistent(String msg, String number, Blip blip,
         Wavelet wavelet) {
      // Create the message object.
      Message message = new Message(msg, number, blip.getCreator(), wavelet
            .getWaveId(), wavelet.getWaveletId(), blip.getBlipId());

      // Store the message in the data store.
      PersistenceManager pm = PMF.get().getPersistenceManager();
      pm.makePersistent(message);
      pm.close();

      // Return the now persistent message.
      return message;
   }

   /**
    * Send the SMS to the receiver using the SMS Send & Receive callback.
    * 
    * @param message
    *           The message to send.
    * @return A response with information to the user.
    * @throws IOException
    *            If things go wrong when contacting the messaging server.
    */
   private String sendTextMessage(Message message) throws IOException {
      // Send message to user.
      String fullMsg = message.getSender() + " from Wave: " + message.getMsg()
            + " (Reply: smswave " + message.getId() + " msg)";

      // Send the request to the Ericsson Labs server.
      HttpHelperUtil.sendSynchronousHttpGetRequest(LABS_SEND_URL + "?key=" + KEY
            + "&to=" + message.getMsisdn() + "&message="
            + HttpHelperUtil.urlEncode(fullMsg));

      return "<img src=\"http://messy-robot.appspot.com/_wave/img/msg_sent.png\" />"
            + " SMS successfully sent!";
   }

   /**
    * Check if there are any un-published
    * 
    * @param bundle
    */
   @SuppressWarnings("unchecked")
   private void processCronUpdate(RobotMessageBundle bundle) {
      int attempt = 0;
      while (attempt++ < 3) {
         try {
            PersistenceManager pm = PMF.get().getPersistenceManager();
            List<Message> messages = (List<Message>) pm.newQuery(Message.class)
                  .execute();
            for (Message message : messages) {
               // Check for unpublished replies.
               if (message.hasReply() && !message.isReplyPublished()) {
                  log.info("Found un-published reply");
                  Blip blip = getBlipForMessage(message, bundle);
                  if (blip != null) {
                     // Publish the reply back into the Wave.
                     publishReplyToWavelet(message, blip);
                     message.setIsReplyPublished(Boolean.TRUE);
                     log.info("Reply successfully published to Wave");
                  } else {
                     log.warning("Could not find Wave with specified ID");
                  }
               }
               // Store the updated message.
               pm.makePersistent(message);
            }
            pm.close();
            break;
         } catch (DatastoreTimeoutException e) {
            // Do nothing.
         }
      }
   }

   /**
    * Find the blip that held the original message.
    * 
    * @param message
    *           The message object.
    * @param robotMessageBundle
    *           Robot bundle.
    * @return The original blip.
    */
   private Blip getBlipForMessage(Message message,
         RobotMessageBundle robotMessageBundle) {
      Blip blip = robotMessageBundle.getBlip(message.getWaveID(), message
            .getWaveletID(), message.getBlipID());
      return blip;
   }

   /**
    * Publish the reply message back to the Wave.
    * 
    * @param messag
    *           The message that now holds the reply.
    * @param blip
    *           The original blip.
    */
   private void publishReplyToWavelet(Message message, Blip blip) {
      Blip replyBlip = blip.createChild();
      TextView textView = replyBlip.getDocument();
      textView.append("Reply: " + message.getReplyMsg());
      //textView.append("test");
      /*
      textView
            .appendMarkup("<img src=\"http://messy-robot.appspot.com/_wave/img/reply_small.png\" /> "
                  + message.getReplyMsg());
                  */
   }
}
